using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using Framework;
using System.Net;
using System.Web.Script.Serialization;
using SchemaMonitor.json;
using SchemaMonitor.json.dict;

namespace SchemaMonitor
{
    //Table-Row Class (Customisable half)
    public partial class CUser
    {
        #region Constants
        //Join Expressions
        //private static string JOIN_SAMPLE = String.Concat(TABLE_NAME, " LEFT OUTER JOIN ", CSample.TABLE_NAME, " ON UserSampleId=SampleId");
        #endregion

        #region Constructors (Public)
        //Default Connection String
        public CUser() : base() {}
        public CUser(long userId) : base(userId) {}
        
        //Alternative Connection String
        public CUser(CDataSrc dataSrc) : base(dataSrc) {}
        
        public CUser(CDataSrc dataSrc, long userId) : base(dataSrc, userId) {}
        
        //Transactional (shares an open connection)
        protected internal CUser(CDataSrc dataSrc, long userId, IDbTransaction txOrNull) : base(dataSrc, userId, txOrNull) {}        
        #endregion

        #region Default Values
        protected override void InitValues_Custom()
        {
            //Custom default values (e.g. DateCreated column)
            _userCreated = DateTime.Now;
            _userUpdated = DateTime.Now;
            _userIsActive = true;
            _userSchemaId = CSchema.DefaultId;

            //Member variables (e.g. for child collections)
            _nodes = new CNodeList();
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion
        
        #region Members
        //Foreign Keys       
        [NonSerialized()] private CNode _userNode;

        //Child Collections  
        [NonSerialized()] private CNodeList _nodesArchived;  
        [NonSerialized()] private CNodeList _nodes;  
        [NonSerialized()] private json.CNodeRootList _inserts;
        [NonSerialized()] private CLog _log;


        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CSchema Schema { get { return CSchema.Cache.GetById(this.UserSchemaId); } }

        public void ClearData()
        {
            _inserts = null;
            _nodes = null;
        }
        public json.CNodeRoot Root { get { return UserNode.Root; } set { UserNode.Root = value; } }
        public json.CSoftUser SoftUser { get { return new CSoftUser(Root, this); } }
        public json.CNodeRootList  Inserts
        {
            get
            {
                if (null == _inserts)
                    _inserts = new CNodeRootList(Nodes, UserId);
                return _inserts;
            }
        }
        public SchemaMonitor.CNodeList All { get { return Nodes; } set { _nodes = value; } }

        public CLog Log
        {
            get
            {
                if (null == _log)
                    _log = new CLog(this);
                return _log;
            }
        }

        public CType Type {  get { return CSoftUser.Type; } }
        public CNode UserNode
        {
            get
            {
                if (null == _userNode)
                    if (UserNodeGuid != Guid.Empty && UserTypeId > 0)
                        _userNode = new CNode().GetById(UserNodeGuid, true);

                if (null == _userNode)
                {
                    _userNode = new CNode();
                    _userNode.NodeTypeId   = Type.TypeId;
                    _userNode.NodeUserId = this.UserId;
                    _userNode.NodeDataAuto = new byte[] { };
                }

                return _userNode;
            }
            set
            {
                _userNode = null;
                if (null == value)
                {
                    UserNodeGuid = Guid.Empty;
                    return;
                }

                if (value.NodeGuid == Guid.Empty)
                {
                    value.NodeGuid = Root.Guid;
                    value.NodeUserId = UserId;
                    value.Save();
                }

                if (UserNodeGuid != value.NodeGuid)
                {
                    UserNodeGuid = value.NodeGuid;
                    UserTypeId = value.NodeTypeId;
                    Save();
                }
            }
        }

        //Relationships - Collections (e.g. children)
        public    CBookList Books                     {  get { return CBook.Cache.GetByUserId(          this.UserId); }  }
        protected CBookList Books_(IDbTransaction tx) {        return new CBook(DataSrc).SelectByUserId(this.UserId, tx); } //Only used for cascade deletes


		public CNodeList Archived
		{
			get
			{
				if (_nodesArchived == null)
				{
					lock (this)
					{
						if (_nodesArchived == null)
						{
							var key = string.Concat("_nodesArchived", this.UserId);
							_nodesArchived = (CNodeList)CCache.Get(key);
							if (null == _nodesArchived)
							{
								var pi = new CPagingInfo(100000);
								pi.TableName = CNodesArchived.TABLE_NAME;
								_nodesArchived = new CNode(DataSrc).SelectByUserId(pi, this.UserId);
								_nodesArchived.User = this;
								if (_userId > 0)
									CCache.Set(key, _nodesArchived);
							}
						}
					}
				}
				return _nodesArchived;
			}
		}


		public CNodeList Nodes
        {
            get
            {
                if (_nodes == null)
                {
                    lock (this)
                    {
                        if (_nodes == null)
                        {
							var key = string.Concat("user", this.UserId);
							_nodes = (CNodeList)CCache.Get(key);
							if (null == _nodes)
							{
								_nodes = new CNode(DataSrc).SelectByUserId(this.UserId);
								_nodes.User = this;
								if (_userId > 0)
									CCache.Set(key, _nodes);
							}
						}
					}
                }
                return _nodes;
            }
            set
            {
                _nodes = value;
                if (null != value)
                    _nodes.User = this;
            }
        }
        protected CNodeList Nodes_(IDbTransaction tx)   //Only used for cascade deletes
        {
            return new CNode(DataSrc).SelectByUserId(this.UserId, tx);
        }
        public int NodesCount()
        {
            if (null != _nodes) return _nodes.Count;
            return new CNode(DataSrc).SelectCountByUserId(this.UserId);
        }

		public TimeSpan LastCheckElapsed()
		{
			var d = LastCheck();
			if (d == DateTime.MinValue)
				return TimeSpan.MaxValue;
			return DateTime.Now.Subtract(d);
		}
		public DateTime LastCheck()
		{
			var pi = new CPagingInfo(1);
			pi.SortByColumn = "CheckId";
			pi.Descending = true;
			var cc = new CCheck().SelectByUserId(pi, this.UserId);
			if (cc.Count == 0)
				return DateTime.MinValue;
			return cc[0].CheckStarted;
		}


		protected CCheckList Checks_(IDbTransaction tx)   //Only used for cascade deletes
        {
            return new CCheck(DataSrc).SelectByUserId(this.UserId, tx);
        }
        public int ChecksCount()
        {
            //if (null != _checks) return _checks.Count;
            return new CCheck(DataSrc).SelectCountByUserId(this.UserId);
        }
        #endregion


        #region Properties - Customisation
        public string UrlGetByType(CType t)
        {
            return CSchema.Only.UrlGetByType(t, this);
        }
        public string UrlGetToken
        {
            get { return CSchema.Only.UrlGetToken(this); }
        }
        public string UrlExtendToken(string userToken)
        {
            return CSchema.Only.UrlExtendToken(this);
        }
        public string UrlExtendToken()
        {
            return UrlExtendToken(UserToken);
        }
        public string UrlGetMetadata(string fullId)
        {
            return CSchema.Only.UrlGetMetadata(fullId, UserToken);
        }


        public CPage GetByType(CType t, WebClient wc, JavaScriptSerializer jss)
        {
            Console.Write("Downloading " + t.TypeName + "s...");
            CPage p = CEngine.DownloadPage(UrlGetByType(t), wc, jss);
            Console.WriteLine(p.Summary);
            return p;
        }
        public CToken GetToken(WebClient wc)
        {
            Console.WriteLine("Downloading Token...");
            var dict = CEngine.DownloadDict(UrlGetToken, wc);
            return new CToken(dict);
        }
        public CDebugToken ExtendToken(WebClient wc)    //also DebugToken
        {
            Console.WriteLine("Extending Token...");
            var dict = CEngine.DownloadDict(UrlExtendToken(), wc);
            return new CDebugToken(dict);
        }
        #endregion

        #region Encryption

        public string UserToken //Decrypt (and Reencrypt any plain text
        {
            get
            {
                //One-off (move from temp colump)
                var temp = UserTokenTemp;
                if (!string.IsNullOrEmpty(temp))
                    this.UserToken = temp;

                //Normal
                var encr = UserTokenEncr;
                if (null != encr)
                    return CBinary.DecryptRijndaelAsStr(encr);

                //None
                return string.Empty;
            }
            set
            {
                if (null == value)
                {
                    UserTokenEncr = null;
                    UserTokenTemp = null;
                }
                else
                {
                    UserTokenEncr = CBinary.EncryptRijndael(value);
                    UserTokenTemp = null;
                    UserTokenHasExpired = false;
                    UserIsActive = true;
                }
                Save();
            }
        }
        #endregion

        #region Save/Delete Overrides
        public override void Save(IDbTransaction txOrNull)
        {
            //Save
            UserUpdated = DateTime.Now;
            base.Save(txOrNull);

            if (null != _userNode)
            {
                var n = _userNode;
                if (n.NodeGuid != Guid.Empty || n.Root.HasId)
                {
					var r = n.Root;
					UserNodeGuid = r.Guid;
                    if (_userNodeGuid != Guid.Empty)
                    {
						//Insert node
						if (n.NodeGuid != r.Guid)
						{
							n.Delete();
							n = new CNode(r, this);
						}
                        n.NodeUserId = UserId;
                        n.Save(txOrNull);

                        //Link to from user, save again
                        _userNodeGuid = n.NodeGuid;
                        _userTypeId = n.NodeTypeId;
                        base.Save(txOrNull);
                    }
                    //Update Node
                    else if (n.HasChanged)
                        n.Save();
                }
            }
        }

        public override void Delete(IDbTransaction txOrNull)
        {
            //Use a transaction if none supplied 
            if (txOrNull == null)
            {
                BulkDelete(this);
                return;
            }

            //Cascade-Delete (all child collections) 
            this.Checks_(txOrNull).DeleteAll(txOrNull);
            this.Nodes_(txOrNull).DeleteAll(txOrNull);


            //Normal Delete 
            base.Delete(txOrNull);
        }
        #endregion

        #region Custom Database Queries
        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)
        //                see also: SelectBy[FK], Search and Count (auto-generated sample queries)
        public CNameValueList CountByTypes()
        {
            return new CNode(this.DataSrc).CountsByTypesForUserId(this.UserId);
        }


        public static CUserList InScope(bool isActive)
        {
            return InScope(new CNameValueList("UserIsActive", isActive));
        }
        public static CUserList InScope(CNameValueList nv)
        {
            return InScope(new CCriteriaList(nv));
        }
        public static CUserList InScope(CCriteriaList where)
        {
            where = CSchema.InScope(where, "UserSchemaId");
            return new CUser().SelectWhere(where);
        }
        #endregion

        #region Searching (Optional)
        //Dynamic search methods: (overload as required for common search patterns, cascade the BuildWhere overloads)
        //   Public  x5 - Simple, Paged, Transactional, Count, and Dataset
        //   Private x1 - BuildWhere
        //See also in-memory search options in list class, such as GetBy[FK] and Search

        //Simple
        public CUserList SelectSearch(string nameOrId /*, schemaId, externalId, isActive*/)  {   return SelectWhere(BuildWhere(nameOrId /*, schemaId, externalId, isActive*/));  } //, JOIN_OR_VIEW); }

        //Paged
        public CUserList SelectSearch(CPagingInfo pi, string nameOrId /*, int schemaId, long externalId, bool? isActive */)
        {
            //pi.TableName = JOIN_OR_VIEW
            return SelectWhere(pi, BuildWhere(nameOrId /*, schemaId, externalId, isActive*/));
        }

        //Transactional
        public CUserList SelectSearch(string nameOrId, /* int schemaId, long externalId, bool? isActive, */ IDbTransaction tx)   {   return SelectWhere(BuildWhere(nameOrId /*, , schemaId, externalId, isActive */), tx);   }   //, JOIN_OR_VIEW, tx); }

        //Dataset (e.g. ExportToCsv)
        public DataSet SelectSearch_Dataset(string nameOrId /*, int schemaId, long externalId, bool? isActive*/)   {   return SelectWhere_Dataset(BuildWhere(nameOrId /*, , schemaId, externalId, isActive */));   }   //, JOIN_OR_VIEW); }

        //Count
        public int SelectCount(string nameOrId /*, int schemaId, long externalId, bool? isActive*/)   {   return SelectCount(BuildWhere(nameOrId /*, , schemaId, externalId, isActive */));   }   //, JOIN_OR_VIEW); }

        //Filter Logic
        private CCriteriaList BuildWhere(string nameOrId /*, int schemaId, long externalId, bool? isActive */)
        {
            CCriteriaList where = new CCriteriaList();  //Defaults to AND logic
            
            //Simple search box UI
            if (!string.IsNullOrEmpty(nameOrId)) 
            {
                //Interpret search string in various ways using OR sub-expression
                CCriteriaGroup orExpr = new CCriteriaGroup(EBoolOperator.Or);
                
                //Special case - search by PK
                /* 
                int id = 0;
                if (int.TryParse(nameOrId, out id))
                    orExpr.Add("UserId", id); 
                */
                
                //Search a range of string columns
                string wildCards = string.Concat("%", nameOrId, "%");
                orExpr.Add("UserName", ESign.Like, wildCards);

                //Conclude
                if (orExpr.Group.Count > 0)
                    where.Add(orExpr);
            }
            
			//Other search Colums (customise as required)
            //if (int.MinValue != schemaId)   where.Add("UserSchemaId", schemaId);
            //if (long.MinValue != externalId)   where.Add("UserExternalId", externalId);
            //if (isActive.HasValue)    where.Add("UserIsActive", true);     //Customise bool filters according to UI (e.g. for checkbox, use simple bool and bias in one direction)

            return where;
        }
        #endregion


        #region Cloning
        public CUser Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CUser copy = new CUser(this, target);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            copy.Save(txOrNull);

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.UserId);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this..Example)
        }
        #endregion

    }
}